package com.tt.easycache;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import android.content.Context;
import android.os.Environment;
import android.os.Message;

/**
 * 采用文件缓存<br>
 * 用key-value的方式保存 <Br>
 * 如何使用：<br>
 * 1.获取 {@link KvCache} 对象，use {@link KvCache#getInstance(Context)}<br>
 * 2. {@link KvCache#open()} 并返回打开状态（false的话就不用存取了）<br>
 * 3.使用 {@link KvCache#put(String, String)} 或者
 * {@link KvCache#get(String, IOnFetchCompleteListener)}方法存储数据
 * 
 * @author Kyson
 */
public class KvCache {
    private static volatile KvCache mInstance;

    private Context mContext;

    private KvCache(Context context) {
        this.mContext = context;
    }

    public static synchronized KvCache getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new KvCache(context);
        }
        return mInstance;
    }

    /**
     * 存储完成 <功能简述> <Br>
     * <功能详细描述> <Br>
     * 
     * @author Kyson
     */
    public interface IOnCacheCompleteListener {
        void onCacheComplete(boolean isSuccess);
    }

    /**
     * 获取到缓存结果的监听 <功能简述> <Br>
     * <功能详细描述> <Br>
     * 
     * @author Kyson
     */
    public interface IOnFetchCompleteListener {
        void onFetchComplete(String key, String value);
    }

    /**
     * 初始化缓存路径
     * <功能简述>
     * @param dirName
     */
    public void init(String dirName) {
        FileUtil.setDir(mContext, dirName);
    }

    /**
     * 打开文件缓存<br>
     * 此方法其实就是检查文件路径，没有就创建，及一些其他的准备工作 <功能简述>
     * 
     * @return 打开成功或失败
     */
    public boolean open() {
        return FileUtil.check();
    }

    /**
     * 是否存在缓存 <功能简述>
     * 
     * @param key
     * @return
     */
    public boolean isExist(String key) {
        return FileUtil.isExistCache(keyToMD5(key));
    }

    /**
     * 缓存是否还有效 <功能简述>
     * 
     * @param key
     * @param validDuration
     * @return
     */
    public boolean isValid(String key, long validDuration) {
        long lastModified = FileUtil.getCacheTime(keyToMD5(key));
        if ((getSystemTime() - lastModified) > validDuration) {//超出有效期
            return false;
        }
        return true;
    }

    /**
     * 缓存是否可用<br>
     * 存在且有效 <功能简述>
     * 
     * @param key
     * @param validDuration
     * @return
     */
    public boolean isCacheEnable(String key, long validDuration) {
        return isExist(key) && isValid(key, validDuration);
    }

    /**
     * 取出文件缓存<br>
     * 使用之前最好先检查是否有缓存 use {@link KvCache#isExist(String)}<br>
     * 
     * @param key
     * @param onFetchCompleteListener
     *            获取到缓存数据,该回调在调用此方法线程中
     */
    public void get(final String key,
            final IOnFetchCompleteListener onFetchCompleteListener) {
        final String keyOfMD5 = keyToMD5(key);

        final WeakHandler handler = new WeakHandler(mContext) {

            @Override
            public void receiveMsg(Message msg) {
                if (onFetchCompleteListener != null) {
                    onFetchCompleteListener.onFetchComplete(key,
                            String.valueOf(msg.obj));
                }
            }
        };
        new Thread(new Runnable() {

            @Override
            public void run() {
                String value = FileUtil.readFromFile(keyOfMD5);
                handler.sendMessage(handler.obtainMessage(0, value));
            }
        }).start();
    }

    /**
     * 用键值对形式存入string数据 <功能简述> <功能简述>
     * 
     * @param key
     * @param value
     * @param onCacheCompleteListener
     *            通过此监听存储的结果，该回调在调用此方法线程中
     */
    public void put(final String key, final String value,
            final IOnCacheCompleteListener onCacheCompleteListener) {
        final String keyOfMD5 = keyToMD5(key);
        final WeakHandler handler = new WeakHandler(mContext) {

            @Override
            public void receiveMsg(Message msg) {
                if (onCacheCompleteListener != null) {
                    onCacheCompleteListener.onCacheComplete((Boolean) msg.obj);
                }
            }
        };
        new Thread(new Runnable() {

            @Override
            public void run() {
                boolean isSuccess = FileUtil.writeToFile(keyOfMD5, value);
                handler.sendMessage(handler.obtainMessage(0, isSuccess));
            }
        }).start();
    }

    /**
     * 用键值对形式存入string数据 <功能简述>
     * 
     * @param key
     * @param value
     */
    public void put(final String key, final String value) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                FileUtil.writeToFile(keyToMD5(key), value);
            }
        }).start();
    }

    public static class FileUtil {

        /**
         * 检查缓存目录存在与否 <功能简述>
         * 
         * @return
         */
        private static boolean isExistCache(String fileName) {
            //遍历目录，找出是否存在缓存
            if (!Environment.getExternalStorageState().equals(
                    Environment.MEDIA_MOUNTED)) {
                //sd卡没有挂载
                return false;
            }
            File file = new File(getCacheFileFullPath(fileName));
            return file.exists();
        }

        /**
         * 获取文件最近修改时间 <功能简述>
         * 
         * @param fileName
         * @return
         */
        private static long getCacheTime(String fileName) {
            //遍历目录，找出是否存在缓存
            if (!Environment.getExternalStorageState().equals(
                    Environment.MEDIA_MOUNTED)) {
                //sd卡没有挂载
                return 0;
            }
            File file = new File(getCacheFileFullPath(fileName));
            return file.lastModified();
        }

        /**
         * SD卡根路径
         */
        private static final String SDCARD_PATH = Environment
                .getExternalStorageDirectory().getAbsolutePath();

        /**
         * 缓存的目录
         */
        private static String sDir = SDCARD_PATH;

        /**
         * 设置默认的文件缓存路径 <功能简述>
         * 
         * @param context
         * @param dirName
         */
        private static void setDir(Context context, String dirName) {
            sDir = SDCARD_PATH + "/Android/data/" + context.getPackageName() + "/cache/"
                    + dirName;
        }

        /**
         * 检查目录环境是否正常 <功能简述>
         * 
         * @return
         */
        private static boolean check() {
            if (!Environment.getExternalStorageState().equals(
                    Environment.MEDIA_MOUNTED)) {
                //sd卡没有挂载
                return false;
            }
            //检查缓存目录存在与否
            File dir = new File(sDir);
            if (!dir.exists()) {//目录不存在，创建
                if (!dir.mkdirs()) {//创建失败
                    return false;
                }
            }
            return true;
        }

        /**
         * 获取文件的全路径 <功能简述>
         * 
         * @param name
         *            文件名
         * @param suffix
         *            文件后缀，默认temp
         * @param dirPath
         *            文件父目录
         * @return 全路径
         */
        private static String getCacheFileFullPath(String name) {
            StringBuilder sb = new StringBuilder();
            sb.append(sDir);
            sb.append("/");
            sb.append(name);
            return sb.toString();
        }

        /**
         * 从全路径的文件中读取字符串 <功能简述>
         * 
         * @param path
         * @return 读出来的字符串，发生错误返回null
         */
        public static String readFromFile(String fileName) {
            String readOutStr = "";
            File file = new File(getCacheFileFullPath(fileName));
            if (!file.exists()) {//文件不存在
                return null;
            }
            BufferedReader bufReader = null;
            try {
                bufReader = new BufferedReader(new FileReader(file));
                String line = "";
                while ((line = bufReader.readLine()) != null) {
                    readOutStr += line;
                }
            } catch (FileNotFoundException e) {
                return null;
            } catch (IOException e) {
                return null;
            } finally {
                try {
                    if (bufReader != null) {
                        bufReader.close();
                    }
                } catch (IOException e) {
                    return null;
                }
            }
            return readOutStr;
        }

        /**
         * 写入文件 <功能简述><br>
         * 耗时操作<br>
         * 写入成功之后记录存入数据库<br>
         * 此方法已经默认文件环境正常（检查只是防止崩溃）
         * 
         * @param fileName
         *            文件名，用url
         * @param value
         *            写入的字符串
         * @return 是否写成功
         */
        public static synchronized boolean writeToFile(String fileName,
                String value) {
            if (!check()) {
                return false;
            }
            //创建名称与url相符的文件
            File file = new File(getCacheFileFullPath(fileName));

            if (file.exists()) {//文件存在
                //递归删除文件
                delete(file.getAbsolutePath());
            }
            //创建
            try {
                file.createNewFile();
            } catch (IOException e) {
                return false;
            }
            if (!file.canWrite()) {
                return false;
            }
            //文件存在或者已经创建
            //将字符串写入文件
            FileOutputStream outStream = null;
            try {
                outStream = new FileOutputStream(file);
                outStream.write(value.getBytes());
            } catch (FileNotFoundException e) {
                return false;
            } catch (IOException e) {
                return false;
            } finally {
                try {
                    if (outStream != null) {
                        outStream.close();
                    }
                } catch (IOException e) {
                    return false;
                }
            }
            return true;
        }

        /**
         * 删除文件
         * 
         * @param file
         * @return
         */
        private static boolean deleteFile(File file) {
            if (!file.exists()) {
                return false;
            }
            if (!file.canRead()) {
                return false;
            }
            return file.delete();
        }

        /**
         * 删除文件夹
         * 
         * @param path
         * @return
         */
        private static boolean deleteDirectory(String path) {
            File srcFile = new File(path);
            if (!srcFile.exists()) {
                return false;
            }
            if (!srcFile.canRead()) {
                return false;
            }
            File[] files = srcFile.listFiles();
            for (File filetemp : files) {
                if (filetemp.isFile()) {
                    boolean b = deleteFile(filetemp);
                    if (!b) {
                        return false;
                    }
                } else {
                    boolean b = deleteDirectory(filetemp.getAbsolutePath());
                    if (!b) {
                        return false;
                    }
                }
            }
            return srcFile.delete();
        }

        /**
         * 删除文件或者文件夹
         * 
         * @param path
         * @return
         */
        public static boolean delete(String path) {
            File file = new File(path);
            if (file.isDirectory()) {
                return deleteDirectory(path);
            } else {
                return deleteFile(file);
            }
        }
    }

    /**
     * 键值转换为md5加密 <功能简述>
     * 
     * @param key
     * @return
     */
    private static String keyToMD5(String key) {
        String md5String = "";
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.update(key.getBytes());
            byte messageDigestByteArray[] = messageDigest.digest();
            if (messageDigestByteArray == null
                    || messageDigestByteArray.length == 0) {
                return md5String;
            }
            StringBuffer hexSb = new StringBuffer();
            int length = messageDigestByteArray.length;
            for (int i = 0; i < length; i++) {
                hexSb.append(Integer
                        .toHexString(0xFF & messageDigestByteArray[i]));
            }
            md5String = hexSb.toString();
            return md5String;
        } catch (NoSuchAlgorithmException e) {
        }
        return md5String;
    }

    /**
     * 取得系统时间 <功能简述>
     * 
     * @return
     */
    private static long getSystemTime() {
        return System.currentTimeMillis();
    }

}
